﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

namespace PropertyModels.ComponentModel;

/// <summary>
/// Class MultiObjectPropertyDescriptor.
/// Implements the <see cref="PropertyDescriptor" />
/// </summary>
/// <seealso cref="PropertyDescriptor" />
public class MultiObjectPropertyDescriptor : PropertyDescriptor, IEnumerable<PropertyDescriptor>
{
    #region MultiObjectAttributes
    /// <summary>
    /// Class MultiObjectAttributes.
    /// Implements the <see cref="AttributeCollection" />
    /// </summary>
    /// <seealso cref="AttributeCollection" />
    private class MultiObjectAttributes : AttributeCollection
    {
        private readonly MultiObjectPropertyDescriptor _owner;
        private AttributeCollection[]? _parentAttributes;
        private readonly Lazy<Attribute[]> _attributes;
        private Hashtable? _attributesTable;

        /// <summary>
        /// Initializes a new instance of the <see cref="MultiObjectAttributes"/> class.
        /// </summary>
        /// <param name="owner">The owner.</param>
        public MultiObjectAttributes(MultiObjectPropertyDescriptor owner)
            : base(null)
        {
            _owner = owner;
            _attributes = new Lazy<Attribute[]>(GetAttributes);
        }

        /// <summary>
        /// Gets the <see cref="Attribute"/> with the specified attribute type.
        /// </summary>
        /// <param name="attributeType">Type of the attribute.</param>
        /// <returns>Attribute.</returns>
        public override Attribute? this[Type attributeType]
        {
            get
            {
                if (_parentAttributes == null)
                {
                    _parentAttributes = new AttributeCollection[_owner.Descriptors.Length];
                    for (var i = 0; i < _parentAttributes.Length; i++)
                    {
                        _parentAttributes[i] = _owner.Descriptors[i].Attributes;
                    }
                }

                if (_parentAttributes.Length == 0)
                {
                    return GetDefaultAttribute(attributeType);
                }

                Attribute? a;
                if (_attributesTable != null)
                {
                    a = (Attribute?)_attributesTable[attributeType];
                    if (a != null)
                    {
                        return a;
                    }
                }

                a = _parentAttributes[0][attributeType];
                if (a == null)
                {
                    return null;
                }

                for (var i = 1; i < _parentAttributes.Length; i++)
                {
                    if (!a.Equals(_parentAttributes[i][attributeType]))
                    {
                        a = GetDefaultAttribute(attributeType);
                        break;
                    }
                }

                _attributesTable ??= [];
                _attributesTable[attributeType] = a;

                return a;
            }
        }

        /// <summary>
        /// Gets the attribute collection.
        /// </summary>
        /// <value>The attributes.</value>
        protected override Attribute[] Attributes => _attributes.Value;

        /// <summary>
        /// Gets the attributes.
        /// </summary>
        /// <returns>Attribute[].</returns>
        private Attribute[] GetAttributes()
        {
            return _owner.Descriptors.First().Attributes.Cast<Attribute>()
                .Select(x => this[x.GetType()])
                .Where(attr => attr != null)
                .Select(attr => attr!)
                .ToArray();
        }
    }
    #endregion

    /// <summary>
    /// Gets the descriptors.
    /// </summary>
    /// <value>The descriptors.</value>
    internal PropertyDescriptor[] Descriptors { get; }

    /// <summary>
    /// Initializes a new instance of the <see cref="MultiObjectPropertyDescriptor"/> class.
    /// </summary>
    /// <param name="descriptors">The descriptors.</param>
    public MultiObjectPropertyDescriptor(PropertyDescriptor[] descriptors)
        : base(descriptors[0].Name, null)
    {
        Descriptors = descriptors;
    }

    /// <summary>
    /// Gets the owner.
    /// </summary>
    /// <param name="list">The list.</param>
    /// <param name="index">The index.</param>
    /// <returns>System.Object.</returns>
    private object? GetOwner(object[] list, int index)
    {
        var res = list[index];
        return res is not ICustomTypeDescriptor custom ? res : custom.GetPropertyOwner(Descriptors[index]);
    }

    /// <summary>
    /// Creates a collection of attributes using the array of attributes passed to the constructor.
    /// </summary>
    /// <returns>A new <see cref="T:System.ComponentModel.AttributeCollection" /> that contains the <see cref="P:System.ComponentModel.MemberDescriptor.AttributeArray" /> attributes.</returns>
    protected override AttributeCollection CreateAttributeCollection() => new MultiObjectAttributes(this);

    /// <summary>
    /// Gets an editor of the specified type.
    /// </summary>
    /// <param name="editorBaseType">The base type of editor, which is used to differentiate between multiple editors that a property supports.</param>
    /// <returns>An instance of the requested editor type, or <see langword="null" /> if an editor cannot be found.</returns>
    public override object? GetEditor(Type editorBaseType) => Descriptors[0].GetEditor(editorBaseType);

    /// <summary>
    /// When overridden in a derived class, returns whether resetting an object changes its value.
    /// </summary>
    /// <param name="component">The component to test for reset capability.</param>
    /// <returns><see langword="true" /> if resetting the component changes its value; otherwise, <see langword="false" />.</returns>
    public override bool CanResetValue(object component)
    {
        var list = (object[])component;
        for (var i = 0; i < Descriptors.Length; i++)
        {
            if (!Descriptors[i].CanResetValue(GetOwner(list, i)!))
            {
                return false;
            }
        }

        return true;
    }

    /// <summary>
    /// Gets a value indicating whether value change notifications for this property may originate from outside the property descriptor.
    /// </summary>
    /// <value><c>true</c> if [supports change events]; otherwise, <c>false</c>.</value>
    public override bool SupportsChangeEvents => Descriptors.Any(p => p.SupportsChangeEvents);

    /// <summary>
    /// Enables other objects to be notified when this property changes.
    /// </summary>
    /// <param name="component">The component to add the handler for.</param>
    /// <param name="handler">The delegate to add as a listener.</param>
    public override void AddValueChanged(object component, EventHandler handler)
    {
        var list = (object[])component;
        for (var i = 0; i < Descriptors.Length; i++)
        {
            if (!Descriptors[i].SupportsChangeEvents)
            {
                continue;
            }

            Descriptors[i].AddValueChanged(GetOwner(list, i)!, handler);
        }
    }

    /// <summary>
    /// Enables other objects to be notified when this property changes.
    /// </summary>
    /// <param name="component">The component to remove the handler for.</param>
    /// <param name="handler">The delegate to remove as a listener.</param>
    public override void RemoveValueChanged(object component, EventHandler handler)
    {
        var list = (object[])component;
        for (var i = 0; i < Descriptors.Length; i++)
        {
            if (!Descriptors[i].SupportsChangeEvents)
            {
                continue;
            }

            Descriptors[i].RemoveValueChanged(GetOwner(list, i)!, handler);
        }
    }

    /// <summary>
    /// When overridden in a derived class, gets the type of the component this property is bound to.
    /// </summary>
    /// <value>The type of the component.</value>
    public override Type ComponentType => Descriptors[0].ComponentType;

    /// <summary>
    /// When overridden in a derived class, gets the current value of the property on a component.
    /// </summary>
    /// <param name="component">The component with the property for which to retrieve the value.</param>
    /// <returns>The value of a property for a given component.</returns>
    public override object? GetValue(object? component)
    {
        var list = (component as IEnumerable)!.Cast<object>().ToArray();
        var res = GetValue(Descriptors[0], GetOwner(list, 0)!);
        for (var i = 0; i < Descriptors.Length; i++)
        {
            var temp = GetValue(Descriptors[i], GetOwner(list, i)!);
            if (res != temp && res?.Equals(temp) == false)
            {
                return null;
            }
        }

        return res;
    }

    /// <summary>
    /// When overridden in a derived class, gets a value indicating whether this property is read-only.
    /// </summary>
    /// <value><c>true</c> if this instance is read only; otherwise, <c>false</c>.</value>
    public override bool IsReadOnly => Descriptors.Any(x => x.IsReadOnly);

    /// <summary>
    /// When overridden in a derived class, gets the type of the property.
    /// </summary>
    /// <value>The type of the property.</value>
    public override Type PropertyType => Descriptors[0].PropertyType;

    /// <summary>
    /// Gets the type converter for this property.
    /// </summary>
    /// <value>The converter.</value>
    // ReSharper disable once RedundantSuppressNullableWarningExpression
    public override TypeConverter Converter => Descriptors[0].Converter!;

    /// <summary>
    /// Gets the name that can be displayed in a window, such as a Properties window.
    /// </summary>
    /// <value>The display name.</value>
    public override string DisplayName => Descriptors[0].DisplayName;

    /// <summary>
    /// When overridden in a derived class, resets the value for this property of the component to the default value.
    /// </summary>
    /// <param name="component">The component with the property value that is to be reset to the default value.</param>
    public override void ResetValue(object component)
    {
        var list = (object[])component;
        for (var i = 0; i < Descriptors.Length; i++)
        {
            Descriptors[i].ResetValue(GetOwner(list, i)!);
        }
    }

    /// <summary>
    /// Gets the values.
    /// </summary>
    /// <param name="components">The components.</param>
    /// <returns>System.Object[].</returns>
    public object?[] GetValues(object[] components)
    {
        var list = new object?[components.Length];
        for (var i = 0; i < Descriptors.Length; i++)
        {
            list[i] = GetValue(Descriptors[i], GetOwner(components, i)!);
        }

        return list;
    }

    /// <summary>
    /// When overridden in a derived class, sets the value of the component to a different value.
    /// </summary>
    /// <param name="component">The component with the property value that is to be set.</param>
    /// <param name="value">The new value.</param>
    public override void SetValue(object? component, object? value)
    {
        var list = (object[])component!;
        for (var i = 0; i < Descriptors.Length; i++)
        {
            object? clonedVal = null;
            if (value is ICloneable cloneable)
            {
                clonedVal = cloneable.Clone();
            }

            Descriptors[i].SetValue(GetOwner(list, i), clonedVal ?? value);
        }
    }

    private static object? GetValue(PropertyDescriptor? pd, object? component)
    {
        object? value = null;
        if (component == null || pd == null)
        {
            return value;
        }

        try
        {
            component = GetPropertyOwner(pd, component);
            value = pd.GetValue(component);
        }
        catch (Exception e)
        {
            value = GetUnWindedException(e).Message;
        }

        return value;
    }

    private static Exception GetUnWindedException(Exception e)
    {
        var result = e;
        if (e is TargetInvocationException)
        {
            result = e.InnerException;
        }

        var message = result!.Message;
        while (string.IsNullOrEmpty(message) && result.InnerException != null)
        {
            message = result.InnerException.Message;
            result = result.InnerException;
        }

        return result;
    }

    private static object GetPropertyOwner(PropertyDescriptor pd, object component) => component is not ICustomTypeDescriptor typeDescriptor ? component : typeDescriptor.GetPropertyOwner(pd)!;

    /// <summary>
    /// When overridden in a derived class, determines a value indicating whether the value of this property needs to be persisted.
    /// </summary>
    /// <param name="component">The component with the property to be examined for persistence.</param>
    /// <returns><see langword="true" /> if the property should be persisted; otherwise, <see langword="false" />.</returns>
    public override bool ShouldSerializeValue(object component)
    {
        var list = (object[])component;
        for (var i = 0; i < Descriptors.Length; i++)
        {
            if (Descriptors[i].ShouldSerializeValue(GetOwner(list, i)!))
            {
                return true;
            }
        }

        return false;
    }

    /// <summary>
    /// Gets the <see cref="PropertyDescriptor"/> at the specified index.
    /// </summary>
    /// <param name="index">The index.</param>
    /// <returns>PropertyDescriptor.</returns>
    public PropertyDescriptor this[int index] => Descriptors[index];

    /// <summary>
    /// Sets the values.
    /// </summary>
    /// <param name="components">The components.</param>
    /// <param name="values">The values.</param>
    /// <exception cref="System.ArgumentOutOfRangeException"></exception>
    public void SetValues(object components, object values)
    {
        var valuesArray = (object[])values;
        var componentsArray = (object[])components;

        if (valuesArray.Length != Descriptors.Length || valuesArray.Length != componentsArray.Length)
        {
            throw new ArgumentOutOfRangeException(nameof(values));
        }

        for (var i = 0; i < Descriptors.Length; i++)
        {
            Descriptors[i].SetValue(componentsArray[i], valuesArray[i]);
        }
    }

    /// <summary>
    /// Gets the name of the category to which the member belongs, as specified in the <see cref="T:System.ComponentModel.CategoryAttribute" />.
    /// </summary>
    /// <value>The category.</value>
    // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
    public override string Category => Descriptors[0].Category ?? string.Empty;

    #region IEnumerable
    /// <summary>
    /// Returns an enumerator that iterates through the collection.
    /// </summary>
    /// <returns>An enumerator that can be used to iterate through the collection.</returns>
    public IEnumerator<PropertyDescriptor> GetEnumerator()
    {
        foreach (var descriptor in Descriptors)
        {
            yield return descriptor;
        }
    }
    /// <summary>
    /// Returns an enumerator that iterates through a collection.
    /// </summary>
    /// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    #endregion
}